"""
    名称：
        英文名称：Module.Board
        中文名称：主窗体棋盘

    定义：
        【类】BoardClass       棋盘类
"""

# 导入相关模块
import tkinter                      # GUI模块
import os                           # 操作系统相关（NT）模块
from Module import AppLocalType     # 自定义：本机类型（枚举）
from Module import AppState         # 自定义：APP状态（枚举）


class BoardClass(tkinter.Frame):
    """
        棋盘类，负责棋盘生成，落子，计算胜负等

        继承自tkinter.Frame类

        方法：
            __init__：初始化
            __create_board：绘制棋盘
            __onBoard_click：处理棋盘点击事件
            __play：执行落子操作，显示棋子
            __compute_point：根据系统点击产生的坐标，计算真实的坐标索引和坐标像素
            __draw_chessman：显示棋子
            __compute_win：计算当前是否有人获胜
            Other_Side_Play：对方落子处理方法
            Init_Board：初始化棋盘、棋子信息
    """

    def __init__(self, master=None):
        """
            Frame初始化

            参数：
                master=None：隶属对象，当前为主窗口
        """

        """
        利用继承tkinter库的Frame类实现棋盘（区域）组件绘制
        """

        # 调用父类初始化方法进行对象初始化
        tkinter.Frame.__init__(self, master, width=801, height=801)

        """
        定义类的属性变量
        """

        # 存储隶属对象（主窗体）
        self.__APP = master
        # 棋子数据二维数组（List）15*15 默认值为0，1为黑子，-1为白子
        self.__ListChessman = [[0 for y in range(15)] for x in range(15)]
        # 棋子对象（lab+image）二维数组（List）15*15 默认为None,有子则为lable+image对象
        self.__ListChessmanObj = [[None for y in range(15)] for x in range(15)]
        # 当前已落子数量（黑白双方合计）
        self.__CountChessman = 0

        """
        调用内部方法，实现棋盘组件的创建和绘制
        """

        # 创建棋盘
        self.__create_board()
        # 绘制棋盘Frame
        self.place(x=0, y=0)

    def __create_board(self):
        """
            绘制棋盘

            实现方式：
                1.创建lable对象
                2.为lable对象加背景
                3.lab对象加载到当前棋盘Frame
        """

        """
        创建棋盘对象并加载到当前棋盘
        """

        # 棋盘背景图像 801*801
        fp = os.getcwd() + r'\StaticResources\board_bg.png'
        img = tkinter.PhotoImage(file=fp)
        # 棋盘
        self.__lab_Board = tkinter.Label(self, image=img, bd=0)  # 设置背景
        self.__lab_Board.image = img  # 设置背景
        self.pack_propagate(tkinter.FALSE)  # 取消父组件几何大小与子组件控制
        self.__lab_Board.place(x=0, y=0)

        """
        为当前棋盘绑定点击事件
        """

        # 绑定点击事件
        self.__lab_Board.bind('<Button-1>', self.__onBoard_click)

    def __onBoard_click(self, event):
        """
            棋盘点击处理方法

            参数：
                event：点击产生的事件
        """

        # 计算坐标
        point = self.__compute_point(event.x, event.y)
        # 判断坐标是否在规定的范围内，是则显示执行显示棋子方法，否则返回
        if point == (-1, -1, -1, -1):
            print("【棋盘】请点击棋盘正确位置\n")
            return
        else:
            self.__play(point)  # 调用显示棋子方法

    def __play(self, p, local=True):
        """
            显示棋子方法

            参数：
                p：落子的坐标信息(x点坐标索引,y点坐标索引,x像素点,y像素点)
                local=True：是否是当前主机点击棋盘产生的棋子，False表示为对方通过传输来的坐标
        """

        """
        首先判断当前APP状态是否处于running状态
        """

        # 判断APP状态
        if self.__APP.AppState != AppState.Running:
            # 非对弈状态直接退出
            print("【棋盘】当前非对弈中...无法落子\n")
            return

        """
        判断是否本地点击并且当前对弈状态True
        """

        # 如果当前为对弈状态 则绘制棋子
        # 无论当前我方的落子还是对方的落子，只有当前为对弈（IsPlay）状态方可执行绘制棋子
        if self.__APP.IsPlay:
            """
            调用绘制方法绘制棋子，并且将棋子信息存入相应的二维数组（list）中
            """
            # 落子（绘制棋子）
            if not self.__draw_chessman(p, islocal=local):
                # 绘制失败，返回
                return

            """
            如果是本地点击产生的落子信息则发送至对手
            只发送坐标索引，坐标像素由对方自行计算
            """
            if local:
                # 调用辅助信息类的Send_Point()方法发送坐标值对手IP(OpponentIP)
                ip = self.__APP.OpponentIP
                self.__APP.Assisted.Send_Point(ip, p[0], p[1])
                # 设置当前对弈状态 关闭当前对对弈状态，对方收到坐标后，自动开启对方自己的对弈状态
                self.__APP.IsPlay = False
                # 切换对弈状态身份提示
                self.__APP.Assisted.Switch_Identity()

            """
            调用__compute_win()方法计算胜负关系
            返回三种状态：
                与当前主机类型一致，默认、执黑、执白
            处理逻辑：
                如果返回信息为默认类型:
                    表示没有人获胜
                如果是非默认:
                    如果与本机主机类型一致，表示我方获胜，否则为对方获胜
            有人获胜后，弹窗提醒，并执行退出对弈操作
            """
            # 计算胜负
            is_win = self.__compute_win()
            # 判断是否有人获胜
            if is_win != AppLocalType.Default:
                if is_win == self.__APP.LocalType:  # 我方获胜
                    self.__APP.Assisted.Show_InfoMsg('胜利', '恭喜你赢了')
                else:  # 对方获胜
                    self.__APP.Assisted.Show_InfoMsg('失败', '很遗憾你输了')
                # 退出对弈
                self.__APP.Assisted.End()
        else:
            print("【棋盘】还没轮到您落子....\n")

    def __compute_point(self, x, y):
        """
            根据点击棋盘系统产生的坐标X,Y计算实际落子的位置坐标

            参数：
                x：点击产生的x坐标
                y：点击产生的y坐标

            返回：tuple (x,y,xpx,ypx)
                x：棋子X坐标索引 从左到右 0-14
                y：棋子Y坐标索引 从上到下 0-14
                xpx：棋子X真实的坐标像素
                ypx：棋子Y真实的坐标像素
        """

        """
        计算原始信息
        """

        # 地板除50减1作为基准坐标索引
        px_index = x//50 - 1
        py_index = y//50 - 1
        # 整除50取余数作为偏移量
        px_index2 = x % 50
        py_index2 = y % 50

        """
        通过原始信息中的偏移量计算真实坐标索引
        """

        # 偏移量超过25（一个格是50，即超过一半）则再基准坐标索引上+1
        if px_index2 > 25:
            px_index = px_index+1
        if py_index2 > 25:
            py_index = py_index+1

        """
        判断坐标信息是否超出了棋盘范围，超出了则返回默认值(-1, -1, -1, -1)
        """

        # 如果基准坐标+偏移量后 小于0 表示点在了棋盘外框的左侧或上部，返回（-1，-1）坐标
        # 如果基准坐标+偏移量后 大于14 表示点在了棋盘外框的右侧或下方，返回（-1，-1）坐标
        if px_index < 0 or py_index < 0 or px_index > 14 or py_index > 14:
            return((-1, -1, -1, -1))

        """
        根据坐标索引重新计算棋子所在的真实像素点
        偏移量：
            棋盘，左上分别有50的边距所以根据棋子索引计算出来的坐标要+50
            棋子，默认已棋子左上为基准点绘制组件，棋子高宽均为41，所以需要-20，让棋子的中间点在指定位置
        特别说明：
            如果使用了place方法的anchor=tkinter.CENTER参数，则棋子偏移量不需要-20
        """

        # 计算坐标点 +50 为棋盘偏移量 -20 -20 为棋子偏移量
        px = px_index*50+50-20
        py = py_index*50+50-20

        """
        返回计算好的坐标信息
        """

        # 没有错误则返回当前坐标
        return((px_index, py_index, px, py))

    def __draw_chessman(self, p, islocal=True):
        """
            绘制棋子

            在制定的坐标上绘制图像，并且将图像信息和坐标信息存入相应的list中

            参数：
                p：落子的坐标
                islocal=True：是否是本机产生落子
            返回：
                落子成功：返回True
                落子失败：返回False
        """

        """
        根据是否是本机产生的落子信息处理落子的样式和棋子数据信息__ListChessman
        """

        # 当前位置已经有棋子了 则不能落子，直接返回
        if self.__ListChessman[p[0]][p[1]] != 0:
            # 返回落子失败
            return False

        # 定义棋子对象所需图片路径
        fpblack = os.getcwd() + (r'\StaticResources\Piece_hei.png')
        fpwhite = os.getcwd() + (r'\StaticResources\Piece_bai.png')
        # 定义棋子上的提示文字的颜色
        c = "white"  # 白色
        # 判断是否本地
        if islocal:
            # 本机落子
            if self.__APP.LocalType == AppLocalType.Black:  # 执黑
                # 创建棋子所需图片
                img = tkinter.PhotoImage(file=fpblack)
                # 设置棋子数据二维数组（List）属性，标记为1,（黑子）
                self.__ListChessman[p[0]][p[1]] = 1
                # 提示字体颜色
                c = "white"
            elif self.__APP.LocalType == AppLocalType.White:  # 执白
                # 创建棋子所需图片
                img = tkinter.PhotoImage(file=fpwhite)
                # 设置棋子数据二维数组（List）属性，标记为-1,（白子）
                self.__ListChessman[p[0]][p[1]] = -1
                # 提示字体颜色
                c = "black"
            else:
                # 当前未设置状态（出BUG了）
                print("【棋盘】当前APP状态未设置执黑or执白\n")
                # 返回落子失败
                return False
        else:
            # 对方落子
            if self.__APP.LocalType == AppLocalType.Black:  # 执黑，则对方执白
                # 创建棋子所需图片
                img = tkinter.PhotoImage(file=fpwhite)
                # 设置棋子数据二维数组（List）属性，标记为-1,（白子）
                self.__ListChessman[p[0]][p[1]] = -1
                # 提示字体颜色
                c = "black"
            elif self.__APP.LocalType == AppLocalType.White:   # 执白，则对方执黑
                # 创建棋子所需图片
                img = tkinter.PhotoImage(file=fpblack)
                # 设置棋子数据二维数组（List）属性，标记为1,（黑子）
                self.__ListChessman[p[0]][p[1]] = 1
                # 提示字体颜色
                c = "white"
            else:
                # 当前未设置状态（出BUG了）
                print("【棋盘】当前APP状态未设置执黑or执白\n")
                # 返回落子失败
                return False

        """
        记录当前已落子数量（黑白双方合计）
        """

        # 落子一个数量+1
        self.__CountChessman = self.__CountChessman+1

        """
        设置棋子对象__ListChessmanObj
        """

        # 设置为lable对象（默认41*41）
        obj = tkinter.Label(self, image=img, fg=c, bd=0,
                            compound=tkinter.CENTER, text=self.__CountChessman)
        # 设置背景
        obj.image = img
        # 根据坐标信息中的xy的像素信息设置偏移量
        obj.place(x=p[2], y=p[3], width=41, height=41)
        # 存入棋子对象二维数组（List）
        self.__ListChessmanObj[p[0]][p[1]] = obj

        """
        返回
        """

        # 返回成功落子
        return True

    def __compute_win(self):
        """
            计算胜负

            返回：AppLocalType对象
                AppLocalType.Default：没有人获胜
                AppLocalType.Black：黑旗获胜
                AppLocalType.White：白旗获胜
        """

        # 胜方信息
        is_win = AppLocalType.Default
        # 棋盘可落子数，是否还有空位可以落子
        is_have_blank = 15*15
        # 嵌套循环15*15次 依次判断点是否成功连成5颗
        for x in range(15):
            for y in range(15):
                # 当前位置有棋子存在
                if self.__ListChessman[x][y] != 0:
                    # 落子数-1
                    is_have_blank -= 1
                    # 暂存当前点
                    this_point = self.__ListChessman[x][y]
                    # 根据当前点的信息计算是白子还是黑子
                    if this_point == 1:
                        is_win = AppLocalType.Black
                    else:
                        is_win = AppLocalType.White
                    # 计算横向 x 固定
                    # 首先分别计算y-1,y-2,y-3,y-4四个坐标的数据是否与 x,y 相同，相同N+1，不同跳出
                    # 统计连珠数量如果>=5则获胜
                    # 然后分别计算y+1,y+2,y+3,y+4四个坐标的数据是否与 x,y 相同，相同N+1，不同跳出
                    # 统计连珠数量如果>=5则获胜
                    # 初始化5连珠的数量，当前为1
                    n = 1
                    # 横向向前循环4次获取连珠数量
                    for i in range(1, 5):
                        n_x = x
                        n_y = y-i
                        if n_x < 0 or n_x > 14:
                            break
                        if n_y < 0 or n_y > 14:
                            break
                        if self.__ListChessman[n_x][n_y] == this_point:
                            n = n+1
                        else:
                            break
                    # 连接成功5颗相同
                    if n >= 5:
                        return is_win
                    # 横向向后循环4次获取连珠数量
                    for i in range(1, 5):
                        n_x = x
                        n_y = y+i
                        if n_x < 0 or n_x > 14:
                            break
                        if n_y < 0 or n_y > 14:
                            break
                        if self.__ListChessman[n_x][n_y] == this_point:
                            n = n+1
                        else:
                            break
                    # 连接成功5颗相同
                    if n >= 5:
                        return is_win
                    # 计算纵向 y 固定
                    # 首先分别计算x-1,x-2,x-3,x-4四个坐标的数据是否与 x,y 相同，相同N+1，不同跳出
                    # 统计连珠数量如果>=5则获胜
                    # 然后分别计算x+1,x+2,x+3,x+4四个坐标的数据是否与 x,y 相同，相同N+1，不同跳出
                    # 统计连珠数量如果>=5则获胜
                    # 初始化5连珠的数量，当前为1
                    n = 1
                    # 纵向向上循环4次获取连珠数量
                    for i in range(1, 5):
                        n_x = x-i
                        n_y = y
                        if n_x < 0 or n_x > 14:
                            break
                        if n_y < 0 or n_y > 14:
                            break
                        if self.__ListChessman[n_x][n_y] == this_point:
                            n = n+1
                        else:
                            break
                    # 连接成功5颗相同
                    if n >= 5:
                        return is_win
                    # 纵向向下循环4次获取连珠数量
                    for i in range(1, 5):
                        n_x = x+i
                        n_y = y
                        if n_x < 0 or n_x > 14:
                            break
                        if n_y < 0 or n_y > 14:
                            break
                        if self.__ListChessman[n_x][n_y] == this_point:
                            n = n+1
                        else:
                            break
                    # 连接成功5颗相同
                    if n >= 5:
                        return is_win
                    # 计算左上、右下
                    # 首先分别计算xy-1,xy-2,xy-3,xy-4四个坐标的数据是否与 x,y 相同，相同N+1，不同跳出
                    # 统计连珠数量如果>=5则获胜
                    # 然后分别计算xy+1,xy+2,xy+3,xy+4四个坐标的数据是否与 x,y 相同，相同N+1，不同跳出
                    # 统计连珠数量如果>=5则获胜
                    # 初始化5连珠的数量，当前为1
                    n = 1
                    # 斜向左上循环4次获取连珠数量
                    for i in range(1, 5):
                        n_x = x-i
                        n_y = y-i
                        if n_x < 0 or n_x > 14:
                            break
                        if n_y < 0 or n_y > 14:
                            break
                        if self.__ListChessman[n_x][n_y] == this_point:
                            n = n+1
                        else:
                            break
                    # 连接成功5颗相同
                    if n >= 5:
                        return is_win
                    # 斜向右下循环4次获取连珠数量
                    for i in range(1, 5):
                        n_x = x+i
                        n_y = y+i
                        if n_x < 0 or n_x > 14:
                            break
                        if n_y < 0 or n_y > 14:
                            break
                        if self.__ListChessman[n_x][n_y] == this_point:
                            n = n+1
                        else:
                            break
                    # 连接成功5颗相同
                    if n >= 5:
                        return is_win
                    # 计算右上、左下
                    # 首先分别计算x+1y-1,x+2y-2,x+3y-3,x+4y-4四个坐标的数据是否与 x,y 相同，相同N+1，不同跳出
                    # 统计连珠数量如果>=5则获胜
                    # 然后分别计算x-1y+1,x-1y+2,x-1y+3,x-1y+4四个坐标的数据是否与 x,y 相同，相同N+1，不同跳出
                    # 统计连珠数量如果>=5则获胜
                    # 初始化5连珠的数量，当前为1
                    n = 1
                    # 斜向右上循环4次获取连珠数量
                    for i in range(1, 5):
                        n_x = x+i
                        n_y = y-i
                        if n_x < 0 or n_x > 14:
                            break
                        if n_y < 0 or n_y > 14:
                            break
                        if self.__ListChessman[n_x][n_y] == this_point:
                            n = n+1
                        else:
                            break
                    # 连接成功5颗相同
                    if n >= 5:
                        return is_win
                    # 斜向左下循环4次获取连珠数量
                    for i in range(1, 5):
                        n_x = x-i
                        n_y = y+i
                        if n_x < 0 or n_x > 14:
                            break
                        if n_y < 0 or n_y > 14:
                            break
                        if self.__ListChessman[n_x][n_y] == this_point:
                            n = n+1
                        else:
                            break
                    # 连接成功5颗相同
                    if n >= 5:
                        return is_win
        # 没有获胜信息则返回Default，判断是否还有落子空间
        if is_have_blank == 0:
            # 无落子空间返回None平局
            return None
        else:
            # 还有，则返回继续
            return AppLocalType.Default

    def Other_Side_Play(self, x, y):
        """
            处理对方落子信息

            参数：
                x：坐标索引
                y：坐标索引
        """

        """
        根据对方落子的坐标索引生成完整的落子坐标信息
        """

        # 计算完整坐标
        point = (x, y, x*50+50-20, y*50+50-20)

        # 执行落子（local=False 非本地落子）
        self.__play(point, local=False)

    def Init_Board(self):
        """
            初始化棋盘、棋子信息

            清空：
                self.__ListChessman
                self.__ListChessmanObj
                self.__CountChessman = 0
        """

        """
        初始化当前已落子数量
        """

        # 当前已落子数量（黑白双方合计）
        self.__CountChessman = 0

        """
        循环15*15次情况空所有棋子信息和棋子对象
        """

        for x in range(15):
            for y in range(15):
                # 处理棋子数据
                self.__ListChessman[x][y] = 0
                # 处理棋子对象数据
                if not(self.__ListChessmanObj[x][y] is None):
                    self.__ListChessmanObj[x][y].place_forget()  # 隐藏
                    self.__ListChessmanObj[x][y].destroy()  # 销毁
                    self.__ListChessmanObj[x][y] = None  # 置空
